package com.my.spring.design;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Zijian Liao
 * @since 1.0.0
 */
public class BeanFactory {

    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(16);
    private final List<String> beanNameList = new ArrayList<>();
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(16);
    private final List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>(16);
    private final ClassLoader classLoader = ApplicationContext.class.getClassLoader();

    public void preInstantiateSingletons(){
        for (String beanName : beanNameList) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            if(beanDefinition.isSingleton() && !beanDefinition.isLazy()){
                getBean(beanName);
            }
        }
    }

    public List<BeanPostProcessor> getBeanPostProcessorList() {
        return beanPostProcessorList;
    }

    public void registerBeanPostProcessor(BeanPostProcessor beanPostProcessor){
        beanPostProcessorList.add(beanPostProcessor);
    }

    public ClassLoader getClassLoader() {
        return classLoader;
    }

    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition){
        if (!beanDefinitionMap.containsKey(beanName)) {
            beanNameList.add(beanName);
            beanDefinitionMap.put(beanName, beanDefinition);
        }
    }

    public List<String> getBeanNamesByType(Class<?> type){
        List<String> beanNames = new ArrayList<>();
        for (String beanName : beanNameList) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            if(type.isAssignableFrom(beanDefinition.getBeanClass())){
                beanNames.add(beanName);
            }
        }
        return beanNames;
    }


    public Object getBean(String beanName){
        synchronized (singletonObjects){
            // 先从缓存中获取
            Object bean = singletonObjects.get(beanName);
            if(bean != null){
                return bean;
            }
            return this.createBean(beanName);
        }
    }

    private Object createBean(String beanName){
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        try {
            // 创建bean
            Object bean = this.doCreateBean(beanDefinition);
            // 将bean存到容器中
            singletonObjects.put(beanName, bean);
            return bean;
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    private Object doCreateBean(BeanDefinition beanDefinition) throws IllegalAccessException {
        Class<?> clazz = beanDefinition.getBeanClass();
        // 实例化bean
        Object bean = this.newInstance(clazz);
        // 填充属性，将字段设值
        this.populateBean(bean, clazz);
        bean = initializeBean(beanDefinition.getBeanName(), bean);
        return bean;
    }

    private Object initializeBean(String beanName, Object bean){
        invokeAwareMethods(beanName, bean);

        for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
            bean = beanPostProcessor.postProcessBeforeInitialization(bean, beanName);
        }

        invokeInitMethods(bean);

        for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
            bean = beanPostProcessor.postProcessAfterInitialization(bean, beanName);
        }
        return bean;
    }

    private void invokeInitMethods(Object bean){
        if(bean instanceof InitializingBean){
            ((InitializingBean)bean).afterPropertiesSet();
        }
    }

    private void invokeAwareMethods(String beanName, Object bean) {
        if(bean instanceof BeanNameAware){
            ((BeanNameAware)bean).setBeanName(beanName);
        }
    }

    private Object newInstance(Class<?> clazz){
        try {
            // 这里只支持默认构造器
            return clazz.getDeclaredConstructor().newInstance();
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    private void populateBean(Object bean, Class<?> clazz) throws IllegalAccessException {
        // 解析class信息，判断类中是否有需要进行依赖注入的字段
        final Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            Autowired autowired = field.getAnnotation(Autowired.class);
            if(autowired != null){
                // 获取bean
                Object value = this.resolveBean(field.getType(), autowired.required());
                if(value != null){
                    field.setAccessible(true);
                    field.set(bean, value);
                }
            }
        }
    }

    private Object resolveBean(Class<?> clazz, boolean required){
        // 暂时只支持classMap只有一个子类的情况
        List<String> beanNames = getBeanNamesByType(clazz);
        if(beanNames.isEmpty()){
            if(required){
                throw new RuntimeException("找不到可以进行依赖注入的bean");
            }
            return null;
        }
        return getBean(beanNames.get(0));
    }
}
